/* C.Frename: safe file rename which works across file systems */

#include <stdio.h>
#include "kernel.h"

#include "utils.h"

/* Rename a file. If a simple OS rename fails, the file is copied.
 * This allows renames across filing system boundaries.
 * If the destination filename exists, the function deletes it (even
 * if locked) first.
 * This function does its best to be totally paranoid about errors, and
 * returns failure if the rename does not work.
 * Returns 0 on success, 1 on failure.
 */
int frename(const char *old, const char *new)
{
	register int result;
	register int n;
	FILE *in, *out;
	_kernel_osfile_block blk;
	char buf[BUFSIZ];

	/* Check the new file. If it exists, and is not a directory,
	 * unlock it (if necessary) and delete it.
	 */
	result = _kernel_osfile (17, new, &blk);

	/* If the file is a directory, or an error occurred, return failure */
	if (result == 2 || result == _kernel_ERROR)
		return 1;

	/* If the file exists and is locked, unlock it */
	if (result == 1 && (blk.end & 0x0008) != 0)
	{
		blk.end &= ~0x0008;
		if (_kernel_osfile(4, new, &blk) == _kernel_ERROR)
			return 1;
	}

	/* If the file exists, delete it */
	if (result == 1 && _kernel_osfile(6, new, &blk) == _kernel_ERROR)
		return 1;

	/* Now try a simple OS rename */
	if (rename(old, new) == 0)
		return 0;

	/* No luck. Get the old file attributes (to ensure that it exists,
	 * and is not locked, and for later copying to the new file).
	 */
	result = _kernel_osfile (17, old, &blk);

	/* If the file is not a simple file, or an error occurred,
	 * or the file is locked, return failure.
	 */
	if (result != 1 || (blk.end & 0x0008) != 0)
		return 1;
	
	/* Now prepare to copy the file */
	if ((in = fopen(old, "rb")) == NULL)
		return 1;

	if ((out = fopen(new, "wb")) == NULL)
	{
		fclose(in);
		return 1;
	}

	/* Copy the file */
	while (!feof(in))
	{
		n = fread(buf, 1, BUFSIZ, in);
		if (ferror(in) || fwrite(buf, 1, n, out) != n)
		{
			fclose(in);
			fclose(out);
			remove(new);
			return 1;
		}
	}

	if (ferror(in) || fclose(in) == EOF || ferror(out) || fclose(out) == EOF)
	{
		remove(new);
		return 1;
	}

	/* Copy the file attributes across */
	if (_kernel_osfile(1, new, &blk) == _kernel_ERROR)
	{
		remove(new);
		return 1;
	}

	/* Delete the old file. The most likely case of error here
	 * is renaming an open file. We left it a bit late to catch
	 * this one, but we can't find out any earlier...
	 */
	if (_kernel_osfile(6, old, &blk) == _kernel_ERROR)
	{
		remove(new);
		return 1;
	}

	return 0;
}
